Making your own REVEN Axion plugin step by step
In this article we will shed light on REVEN Axion’s customisation possibilities by describing step by step how to create a simple plugin.
We will walk you through:
- The specification of our plugin
- The basics of plugin API for REVEN Axion
- The implementation of the plugin’s core mechanics
You can download the complete percent plugin file here. Percent plugin screenshot
Other plugin examples are available in REVEN documentation
Plugin specification
- When triggered from a selected instruction, jump to the matching one
-
Select matching instructions as follows:
- push: next instruction reading pushed value (may not be a pop).
- pop: previous instruction writting popped value (may not be a push).
- sysenter/sysexit
- call/ret
- int/iret
- leave/push ebp
Note that the matching instruction may not exist or may not have been recorded in current trace.
The plugin codename will be percent (referring to vi’s ‘%’ for “goto match”)
Getting called by REVEN Axion
REVEN Axion plugin basics:
- REVEN Axion will call plugin’s
REVEN Axion_callback()
function if it exists when the shortcut associated with a plugin is triggered (multiple commands by plugin is still achievable throughregister_command
from the plugin API). - REVEN Axion will load any plugin in its autoload directories (user’s default autoload directory is
$HOME/.config/tetrane/REVEN Axion-plugins/autoload
). - To reload a plugin you can just type
REVEN Axion.plugins().reload_plugin('my_plugin_name')
in REVEN Axion’s python console instead of restarting the application.
Getting started with a first dummy plugin stub:
- Create a file
my_percent.py
in your autoload directory
def REVEN Axion_callback():
print "percent plugin called"
- Launch REVEN Axion, define a shortcut (using menu
Actions > Edit shortcuts
orF11
by default) for your brand new plugin.
- Try to trigger your plugin by calling its shortcut. See resulting output in the Python console widget
Retrieving the selected instruction
Now that our plugin can be called, let’s retrieve the selected instruction from REVEN Axion’s API.
from REVEN Axion_api import REVEN Axion
def REVEN Axion_callback():
run, seq, instr = REVEN Axion.selected_sequence()
# run: current run name as a string
# seq: index of selected sequence in current run
# instr: index of selected instruction in selected sequence.
msg = "Calling percent plugin on instruction at [%s@%d:%d]" % (run, seq, instr)
# output to python console
print msg
# log message to status bar
REVEN Axion.status_message(msg, 100000)
Tip:
Use REVEN Axion.status_message(msg, duration_ms = 5000)
instead of print
to have your logs printed in the status bar.
Once we find a matching instruction we will need to select it in REVEN Axion’s view, which can be done by using:
REVEN Axion.select_sequence(run_name, sequence_identifier, instruction_index)
Using REVEN API
REVEN Axion’s client may not have all information required to resolve our matching instruction in memory. Hence we will connect to the REVEN project instance to query any missing information.
APIs summary
- REVEN Axion is connected to a REVEN project instance through REVEN’s network interface.
- Plugin can communicate with REVEN Axion through its plugin API.
- Plugin can create its own connection to REVEN project instance to garther information not loaded by REVEN Axion as any standalone Python script.
Connecting to current REVEN project instance
We connect to the current REVEN project instance through REVEN’s Python API and create an execution point object from the selected instruction.
import REVEN
def REVEN Axion_callback():
run, seq, instr = REVEN Axion.selected_sequence()
# get current connection info
host, port = REVEN Axion.connection_info()
# connect to REVEN using its python API (used by REVEN scripts)
client = REVEN.REVEN_connection(host.encode(), port)
# create execution point common in REVEN scripts
point = REVEN.execution_point(run.encode(), seq, instr)
Warning
- Do not forget to import REVEN.
- Why
encode()
? The plugin API, which relies on PythonQt (theREVEN Axion
object), uses utf-8 strings (thanks to Qt) while REVEN’s Python API is using vanilla python strings.
Now that we have our execution point, we can query more data from
REVEN project instance. Let’s define a function
get_matching_instruction(client, point)
which will return the matching instruction’s execution point or None
.
None
is returned if no matching instruction exists or if the matching execution point is not present in current execution trace.
This gives us the following skeleton:
from REVEN Axion_api import REVEN Axion
import REVEN
def get_matching_instruction(client, point):
# to be implemented:
# - retrieve context
# - ss register heuristic
# - esp register heursitic
# - memory heuristic
def REVEN Axion_callback():
run, seq, instr = REVEN Axion.selected_sequence()
host, port = REVEN Axion.connection_info()
client = REVEN.REVEN_connection(host.encode(), port)
point = REVEN.execution_point(run.encode(), seq, instr)
result = get_matching_instruction(client, point)
if result == None or not result.valid():
print "No matching instruction recorded"
return
# select matching instruction
REVEN Axion.select_sequence(result.run_name, result.sequence_identifier, result.instruction_index)
Trivial heuristic monitoring stack segment
This will handle sysenter/sysexit and int/iret.
If the stack segment register ss
is modified by the selected
instruction, we will consider the next instruction writing ss
as
the matching one.
def get_matching_instruction(client, point):
# create a range of size 1 to query context before and after selected instruction
point_range = REVEN.execution_range(point.run_name, point.sequence_identifier, 1,
point.instruction_index)
# empty vector of logical address range to ignore memory
context = client.run_get_running_context_between(point_range,
REVEN.vector_of_logical_address_range())
# ss value before selected instruction
ss_before = context.before.numeric_registers['ss'].value
# ss value after selected instruction
ss_after = context.after.numeric_registers['ss'].value
if ss_before != ss_after:
# ss is modified by instruction
# search next write for ss and return corresponding execution point
return client.run_search_next_register_use(point, forward=(ss_before > ss_after),
read=False, write=True, register_name="ss")
Test it in REVEN Axion:
Jumping from int 0x80
Landed on matching iretd
Going back to previous int 0x80
Monitoring the stack pointer
This will handle push/pop and call/ret.
# retrieving esp value before and after selected instruction
esp_before = context.before.numeric_registers['esp'].value
esp_after = context.after.numeric_registers['esp'].value
if esp_before != esp_after:
if esp_before > esp_after:
# push-like instruction
stack = REVEN.logical_address(ss_after, esp_after)
# search next read of pushed value
return client.run_search_next_memory_use(point, forward=True,
read=True, write=False, address=stack)
if esp_before < esp_after:
# pop-like instruction
stack = REVEN.logical_address(ss_before, esp_before)
# search previous write of popped value
return client.run_search_next_memory_use(point, forward=False,
read=False, write=True, address=stack)
Going further: memory based heuristic
def pick_memory_access(client, point):
"Return first non null logical address accessed by instruction at given execution point or None"
null_logical = REVEN.logical_address(0, 0)
result = None
accesses = client.memory_get_history_instruction(point)
for access in accesses:
if access.logical != null_logical:
if not (result and result.write and not access.write):
result = access
return result
def get_maching_instruction(client, point):
# skipping context retrieval and previous heuristics
# ...
access = pick_memory_access(client, point)
if access != None:
if access.write:
return client.run_search_next_memory_use(point, forward=True,
read=True, write=False, address=access.logical)
if access.read:
return client.run_search_next_memory_use(point, forward=False,
read=False, write=True, address=access.logical)
Pedantic instruction matching
With the current implementation, if a pushed value is read before the pop, the match would be the first reading instruction and not the actual pop. To get the true pop - if it exists - we could monitor esp
value by channeling run_search_next_register_use
calls to find a greater or equal value for esp
. This way, we would even be able to find a pop disguised as an esp
increment (add esp, 0x4
).
Conclusion
In this article, we’ve seen how one can easily implement simple heuristics to add specific functionality to REVEN Axion, thanks to its Python API.
This widget did not require a graphical interface, but had it been the case we could have created our own custom widgets using PythonQt and connected them to REVEN Axion.